Skip to content

Combine near-duplicate test functions into parametrized tests - #74

Merged
jsh9 merged 4 commits into
mainfrom
2026-10-10-parametrize-tests
Oct 11, 2026
Merged

jsh9 merged 4 commits into
mainfrom
2026-10-10-parametrize-tests

Conversation

@jsh9

@jsh9 jsh9 commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

This PR combines test functions that ran the same code with different inputs and expected results into parametrized tests (pytest.mark.parametrize).

Summary

In 15 of the 22 test files, 108 test functions become 39 parametrized tests with 109 cases. Every case still runs and is reported as its own test. Each test ID comes from the original test name or its comment, e.g. test_truncate[on_the_boundary_of_a_layer] or test_filter[band_pass].

The one extra case comes from test_loading_data__test_invalid_unit_names (ground motion). It had two checks, and they are now two cases of test_loading_data__invalid_input.

One more test is renamed, not combined: test_calc_damping_from_stress_strain__case_3 is now test_calc_damping_from_stress_strain__initial_damping_almost_0, after what it tests, because its two former siblings no longer have numbered IDs.

I compared the collected test IDs before and after: every removed test maps to exactly one new case (the renamed test maps to its new name), and the case count per file is the same (except the +1 above). All 241 tests pass, and pre-commit run -a passes.

What was combined

File Combined tests
test_class_Vs_profile.py 12 format__case_* → test_Vs_profile_format; 5 truncate__case_* → test_truncate; 2 add_halfspace; f0_BH/f0_RO
test_class_curves.py HH_x/H4_x fits (single layer, multi-layer DE, multi-layer DEAP); GGmax/damping get_curve_matrix
test_class_parameters.py HH/MKZ multi-layer pairs (from file, from 2D array, file vs. array, list operations); 4 construct_curves; 2 param_serialize; 2 serialize_to_2D_array
test_class_ground_motion.py 3 PGA loading tests; the invalid-input tests; the 4 filters
test_class_site_factors.py 4 "not lenient" error tests; 6 "lenient" tests
test_class_svm.py 2 Vs_cap tests; 2 valid and 3 invalid get_discretized_profile tests
test_helper_site_response.py calc_z1, calc_GGmax_from_stress_strain, calc_damping_from_stress_strain (cases 1–2), fit_all_damping_curves exceptions, align_two_time_arrays normal cases
test_helper_hh_calibration.py calc_OCR, calc_K0, 4 optimization_kernel cases
test_helper_hh_model.py, test_helper_mkz_model.py serialize/deserialize failure tests
test_class_batch_simulation.py init cases 1–3
test_class_damping_calibration.py get_HH_x_param/get_H4_x_param
test_class_hh_calibration.py the 2 incorrect-Tmax tests
test_class_site_effect_adjustment.py the 2 lenient tests
test_helper_simulations.py elastic/rigid linear tests

Notes for review

  • Method parameters: when cases call different methods of the same kind (the filters, get_HH_x_param()/get_H4_x_param(), get_f0_BH()/get_f0_RO()), the parameter is the unbound method (e.g. GM.highpass), not a method name passed to getattr.
  • Keyword-only booleans: boolean parameters are keyword-only (*), following CONTRIBUTING.md, because muff reports FBT001 on boolean test arguments too.
  • Behavior changes (small):
    • Expected-failure cases that used match='' now have no match. This is equivalent, and pytest no longer warns that an empty string always matches (13 warnings → 6).
    • The HH case of the multi-layer list-operations test checked len() twice. It now also checks n_layer, like the MKZ case.
    • The MKZ case of the multi-layer list-operations test now deletes layer 3, like the HH case, so that both cases can share one fixture (it used to delete layer 6). It still checks len() and n_layer afterwards.
    • The rigid case of test_linear now also labels the plot's y-axis, like the elastic case did.
  • Not combined: tests whose assertions or setup differ, e.g. the HH vs. MKZ get_GGmax/get_damping tests, which build their objects with different classes and deserializers. Checks inside a single test function (e.g. test_is_int, test_range_check, test_query_Vs_given_thk) are also left alone, because this PR only combines separate test functions. Turning those into parametrized cases could be a follow-up.
  • CHANGELOG.md has a new entry, and the example notebooks were re-run (second commit), as CONTRIBUTING.md requires.

🤖 Generated with Claude Code

https://claude.ai/code/session_01C1a5vwxyeL4atk9tYUUEoQ


Generated by Claude Code

Test functions that ran the same code with different inputs and expected
results are now one test with `pytest.mark.parametrize`, in 15 of the 22
test files. Each case keeps its own test ID (named after the original
test or its comment), so the same cases still run and are reported
separately: 108 test functions become 40 parametrized tests with 109
cases (the old invalid-unit-names test in test_class_ground_motion.py
had two checks, which are now two cases).

- Where the cases call different methods of the same kind (e.g., the
  four ground-motion filters, `get_HH_x_param()`/`get_H4_x_param()`, or
  `get_f0_BH()`/`get_f0_RO()`), the unbound method is the parameter
- Boolean parameters are keyword-only (FBT001), as in CONTRIBUTING.md
- The expected-failure cases that matched against an empty string
  (`match=''`, which pytest warns always passes) now use no `match`
- The HH case of the multi-layer list-operations test now checks
  `n_layer` after deleting a layer, like the MKZ case did, instead of
  checking `len()` twice

Tests with different assertions or setup are left as they are, and so
are the sequential checks inside one test function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C1a5vwxyeL4atk9tYUUEoQ
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C1a5vwxyeL4atk9tYUUEoQ
- Name the combined HH/H4 and HH/MKZ tests after both models, e.g.,
  `test_HH_x_or_H4_x_fit_single_layer` and
  `test_HH_MKZ_param_multi_layer__can_initiate_object_from_file`, and
  `test_f0` as `test_f0_BH_or_f0_RO`
- Name the cases of `test_construct_curves`, `test_param_serialize`,
  and `test_serialize_to_2D_array` after the original test functions
  (e.g., `from_HH_G_parameters`)
- Move a comment in test_helper_hh_model.py into the `pytest.param()`
  that it describes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C1a5vwxyeL4atk9tYUUEoQ
- Leave out optional arguments in the cases that used to leave them
  out, so that their defaults are tested again: `dt` in the ground
  motion loading tests, and `OCR_upper_limit` in `test_calc_OCR`. The
  cases now pass a `kwargs` dict, like `test_class_svm.py` does
- Pass callables (`lambda gm: gm.pga_in_g`, and lambdas that call
  `get_curve_matrix()` with the keyword argument) instead of attribute
  and keyword names as strings
- Combine the differential-evolution and DEAP multi-layer fitting tests
  into `test_HH_x_or_H4_x_fit_multi_layer`. The DEAP cases still pass
  when DEAP is not installed, as before; an `ImportError` in the
  differential-evolution cases is no longer swallowed
- Share one `multi_layer_case` fixture among the 4 HH/MKZ multi-layer
  parameter tests, instead of repeating the case list. The list
  operations test now deletes layer 3 in both cases (it was 6 for MKZ)
- Rename `test_param_multi_layer__test_list_operations` to
  `test_HH_MKZ_param_multi_layer__list_operations`, and
  `test_calc_damping_from_stress_strain__case_3` to
  `..._initial_damping_almost_0`, after what it tests (its siblings'
  IDs are no longer numbered)
- Pass a copy, not a view, of the module-level `DATA_5_COLUMNS` array
  in the `incorrect_number_of_columns` case

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016MPgzyHE9jy5vP7heBxZhA
This was referenced Oct 11, 2026
This was referenced Oct 11, 2026
@jsh9
jsh9 merged commit a4ab348 into main Oct 11, 2026
12 checks passed
jsh9 pushed a commit that referenced this pull request Oct 11, 2026
#74 was squash-merged into main, so main's files are identical to the
branch's old base. Every conflict is resolved with this branch's version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7fK8T2pPLf3tPCnMCKL1T
jsh9 pushed a commit that referenced this pull request Oct 11, 2026
#74 was squash-merged into main, so main's files are identical to the
branch's old base. Every conflict is resolved with this branch's version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7fK8T2pPLf3tPCnMCKL1T
jsh9 pushed a commit that referenced this pull request Oct 11, 2026
#74 was squash-merged into main, so main's files are identical to the
branch's old base. Every conflict is resolved with this branch's version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7fK8T2pPLf3tPCnMCKL1T
jsh9 pushed a commit that referenced this pull request Oct 11, 2026
#74 was squash-merged into main, so main's files are identical to the
branch's old base. Every conflict is resolved with this branch's version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7fK8T2pPLf3tPCnMCKL1T
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants